<script setup lang="ts">
/**
 * @prop {ISearchForm[]} list - 可选, table-colume的配置项, 会设置到所有内容列, 对象属性与 element plus 官网在 el-table-colume 标签上的属性一致
 *    @typedef {object} ISearchForm
 *          @property {string} label - 必填, 表单名称
 *          @property {string} prop - 必填, 字段名称
 *          @property {'select' | 'switch' | 'input' | 'number' | 'cascader' | 'treeSelect' | 'datePicker' | 'timePicker' | 'timeSelect' | 'radioGroup' | 'rate' | 'slider' | 'upload' | 'slot'} type - 表单种类
 *          @property {any} option - 可选, element plus 官网在 对应类型表单 标签上的属性
 *          @property {object} selectOption - 可选, { options: Array<any>; label?: string; value?: string; } options: 选择框列表, label: 自定义options中显示字段  value: 自定义options中绑定值字段
 * @prop {Record<string, any>} data - 必填, 与父组件双向绑定
 * @prop {ISearchAction[]} action - 可选, table的配置项, 对象属性与 element plus 官网在 el-table 标签上的属性一致
 *    @typedef {object} ISearchAction
 *          @property {string} label - 必填, 按钮文字
 *          @property {string} prop - 必填, 字段名称, 对应 action 事件的参数
 *          @property {'button' | 'dropdown'} type - 必填, 按钮或具有下拉功能的按钮
 *          @property {Partial<ButtonProps>} option - 可选, element plus 官网在 el-button 标签上的属性
 *          @property {object} dropList - type 为 dropdown 时必填,  {prop: string; label: string; icon?: string | Component; disabled?: boolean; divided?: boolean;}
 * @prop {FormRules} rules - 可选, element plus 规定的表单校验类型一致
 * @prop {bool} showSearch - 可选, 是否显示查询按钮，如果不显示查询按钮，那么数据改变时直接触发查询操作
 * @prop {bool} showReset - 可选, 是否显示重置按钮
 * @prop {Partial<FormProps>} config - 可选, 支持 element plus 在 Form 上的属性
 *
 * @event submit - 点击查询按钮时触发
 * @event action - 点击 action 中的按钮触发
 *      @property {string} action - 对应 action 中的 prop 字段
 * @event update:data - 与父组件双向绑定
 */
import { ArrowDown } from '@element-plus/icons-vue';
import { ElButton, ElDropdown, ElDropdownItem, ElDropdownMenu, ElForm, ElFormItem, ElIcon, ElUpload, FormProps, FormRules } from 'element-plus';
import { debounce } from 'lodash-es';
import { nextTick, ref, toRef, toRefs, watch } from 'vue';

import { IForm, ISearchAction } from '@/types';

import FormCollection from './FormCollection.vue';

const prop = withDefaults(
  defineProps<{
    list?: IForm[];
    data?: Record<string, any>;
    action?: ISearchAction[];
    rules?: FormRules;
    showSearch?: boolean;
    showReset?: boolean;
    searchLoading?: boolean;
    config?: Partial<FormProps>;
  }>(),
  {
    list: () => [],
    action: () => [],
    data: () => ({}),
    rules: () => ({}),
    showSearch: true,
    showReset: false,
    searchLoading: false,
  },
);
const { showSearch } = toRefs(prop);

const emit = defineEmits<{
  (event: 'search', data: any, prop: string): void;
  (event: 'actionClick', action: string, params: any[]): void;
  (event: 'update:data', value: any): void;
  (event: 'selectChange', value: any): void;
}>();

const searchForm = ref();
const data = toRef(prop, 'data');
const searchData = ref<Record<string, any>>({});

watch(
  data,
  () => {
    searchData.value = { ...prop.data };
  },
  { deep: true, immediate: true },
);

function validate(callback: (valid: boolean) => void) {
  return searchForm.value?.validate(callback);
}
function resetFields() {
  searchForm.value?.resetFields();
}
function clearValidate() {
  searchForm.value?.clearValidate();
}

defineExpose({
  resetFields,
  validate,
  clearValidate,
});

const actionClick = (method: string, ...args: any[]) => {
  emit('actionClick', method, args);
};
const reset = () => {
  if (!searchForm.value) searchForm.value?.resetFields();
  submit('');
};

const submit = (flag: string) => {
  emit('update:data', { ...searchData.value });
  nextTick(() => {
    emit('search', searchData.value, flag);
  });
};

const submitByForm = (flag: string) => {
  nextTick(() => {
    if (!showSearch.value) submit(flag);
  });
};

const submitByFormWithDebounce = (flag: string) => {
  nextTick(() => {
    if (!showSearch.value) searchDebounce(flag);
  });
};
const searchDebounce = debounce((flag: string) => {
  submit(flag);
}, 300);
</script>

<template>
  <el-form inline ref="searchForm" :model="searchData" class="head-form" :rules="prop.rules" v-bind="config">
    <FormCollection
      v-for="item in list"
      :key="item.prop"
      :item="item"
      v-model:data="searchData[item.prop]"
      @change="submitByForm(item.prop)"
      @changeDebounce="submitByFormWithDebounce(item.prop)"
    >
      <template v-if="item.type === 'slot'" #[item.prop]><slot :name="item.prop" :data="data"></slot></template>
    </FormCollection>
    <el-form-item v-if="list.length !== 0 && (prop.showSearch || prop.showReset)">
      <el-button @click="submit('')" type="primary" v-if="prop.showSearch" :loading="prop.searchLoading">查 询</el-button>
      <el-button @click="reset" v-if="prop.showReset">重 置</el-button>
    </el-form-item>

    <div class="action" v-if="action.length > 0">
      <div class="action-item" v-for="item in action" :key="item.prop" :prop="item.prop">
        <el-dropdown v-if="item.type === 'dropdown'" placement="bottom" @command="actionClick">
          <el-button v-bind="item.option">
            {{ item.label }}<el-icon class="el-icon--right"><arrow-down /></el-icon>
          </el-button>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item
                v-for="e in item.dropList"
                :key="e.prop"
                :command="e.prop"
                :icon="e.icon"
                :disabled="e.disabled"
                :divided="e.divided"
                >{{ e.label }}</el-dropdown-item
              >
            </el-dropdown-menu>
          </template>
        </el-dropdown>
        <el-button v-else-if="item.type === 'button'" @click="actionClick(item.prop)" v-bind="item.option">{{ item.label }}</el-button>
        <el-upload
          v-else-if="item.type === 'upload'"
          :show-file-list="false"
          :autoUpload="false"
          @change="actionClick(item.prop, $event)"
          v-bind="item.uploadOption"
        >
          <el-button v-bind="item.option">{{ item.label }}</el-button>
        </el-upload>
        <slot v-else :name="item.prop" :option="item.option"></slot>
      </div>
    </div>
  </el-form>
</template>

<style scoped>
.head-form {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
}
.action {
  flex: 1;
  display: flex;
  justify-content: flex-end;
  align-items: center;
}
.action-item {
  margin-right: 10px;
  margin-bottom: var(--monose-form-item-margin-bottom);
}
.action :last-child {
  margin-right: 0;
}
:deep(.el-form-item) {
  margin-right: var(--monose-form-item-margin-right) !important;
  margin-bottom: var(--monose-form-item-margin-bottom) !important;
}
:deep(.el-select) {
  --el-select-width: 180px;
}
:deep(.el-input) {
  --el-input-width: 180px;
}
:deep(.el-input-group) {
  width: 240px;
}
</style>
<style>
:root {
  --monose-form-item-margin-bottom: 10px;
  --monose-form-item-margin-right: 15px;
}
</style>
